package streaming;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import simulation.AttendRequest;
import simulation.AvailableChunks;
import simulation.ChunkStatusEnum;
import simulation.Config;
import simulation.FileLog;
import simulation.Logs;
import simulation.Message;
import simulation.PeriodicNotify;
import simulation.PeriodicRequest;
import simulation.PeriodicSend;
import simulation.PeriodicUpdate;
import simulation.Simulator;
import simulation.Statistics;
import simulation.Util;
import simulation.Window;

public class Host {
	Overlay overlay = Overlay.getInstance();
	Simulator simulator = Simulator.getInstance();
	
	Integer id;
	
	
	
	HashMap neighbors = new HashMap(); // Integer, Neighbor pairs
	
	ArrayList<HashMap> neighborsByRing = new ArrayList<HashMap>();
	ArrayList neighborQueue = new ArrayList(); // List of Host objects
	Collection<Host> outstandingNeighbors = new HashSet();
	ArrayList packetsToNotify = new ArrayList();
	ArrayList requestedQueue = new ArrayList();

	

	
	//Glaucio - list of neighbors that I wil not ask for chunks
	Collection<Integer> blacklistedIds;
	Integer cidBlacklistUpdate;
	HashMap<Integer,String> originalHashHit = new HashMap<Integer, String>();
	HashMap<Integer,String> strUSetsBlacklist = new HashMap<Integer, String>();

	Window interestWindow;
	Window availabilityWindow;
	
	PeriodicUpdate periodicUpdate;
	PeriodicNotify periodicNotify;
	PeriodicRequest periodicRequest;
	PeriodicSend periodicSend;
	
	int extraBalance = 0;
	
	int sentCount = 0;
	int receivedCount = 0;
	int prevReceivedCount = 0;
	int prevAvgSent = StreamingConfig.MCASTRATE;
	
	int auxCount = 0;
	
	ArrayList<AvailableChunks> chunks;
	
	Statistics downloadStats = new Statistics();
	Statistics uploadStats = new Statistics();
	
	int updateCount = 0;
	
	ComparatorModule comparatorModule;
	
	
	/**
	 * Constructor of Host
	 * @param newId Id of the host
	 */
	public Host(int newId) {
		this.setId(newId);
		chunks = new ArrayList(StreamingConfig.MCASTBUFFERSIZE);
		Simulator.getInstance().isAttacker[this.getId()] = false;
		
		for (int i = 0; i < StreamingConfig.MCASTBUFFERSIZE; i++){
			AvailableChunks newChunk = new AvailableChunks();
			
			newChunk.setChunkStatus(ChunkStatusEnum.NOT_HEARD_OF);
			
			chunks.add(newChunk);
		}
		
		periodicUpdate =  new PeriodicUpdate(this);
		periodicNotify = new PeriodicNotify(this);
		periodicRequest = new PeriodicRequest(this);
		periodicSend = new PeriodicSend(this);
		
		if (Config.USE_COMPARATOR_MODULE){
			
			this.comparatorModule = new ComparatorModule(this);
			
		}
	}
	
	public void update() {
		
		updateCount++;
		
		// Make this method periodic
		periodicUpdate.addEvent();
		
		extraBalance = 0;
		
		// Remove all old packets
		int delta = StreamingConfig.MCASTRATE;
		
		// Take into account that there might be less than delta elements in packets
		delta = Math.min(delta, chunks.size());
		for (int i = 0; i < delta; i++)
			chunks.remove(0);
		
		// Update windows
		availabilityWindow.update(this.id);
		interestWindow.update(this.id);
		
		// Remove old notifications
		Iterator iterator = neighbors.keySet().iterator();
		Neighbor neighbor;
		
		while (iterator.hasNext()) {
			neighbor = (Neighbor)neighbors.get(iterator.next());
			neighbor.update(interestWindow.getPacketStartSeqNo());
			
			// Update outstanding count
			for (WaitingFor wt : neighbor.waitingFor.values()){
				// Whether the requested chunk is not needed anymore due to be out of interest window or the chunk timeout exceeded 
				if ((!interestWindow.isWithin(wt.getChunkId())) || wt.isTimeout()){
					//if (wt.isTimeout()){ // *** APESAR PRA CHECAR O TIMEOUT!! REMOVER ***
						//System.out.println("Timeout do cid " + wt.getChunkId() + " solicitado pelo vizinho " + this.getId() + " ao peer " + neighbor.id + ". (timeout = " + wt.getTimeRequested() + Simulator.getInstance().getTime() + ")");
					//}
					neighbor.removeRequestedPacket(wt.getChunkId());
					Host n = overlay.getHost(neighbor.id);
					if (outstandingNeighbors.contains(n)) {
						outstandingNeighbors.remove(n);
						neighborQueue.add(n);
					}
				}
			}
			
//			for (int i = 0; i < StreamingConfig.MCASTMAXOUTSTANDING; i++) {
//				int index = neighbor.waitingFor[i]; 
//				/* Check if the package has already been downloaded - left the disponibility window */
//				if ((index != -1) && (!interestWindow.isWithin(index))) {
//						neighbor.removeRequestedPacket(index);
//						Host n = overlay.getHost(neighbor.id);
//						if (outstandingNeighbors.contains(n)) {
//							outstandingNeighbors.remove(n);
//							neighborQueue.add(n);
//						}
//				}
//			}
			
			if (((updateCount % 10) == 0) && (updateCount > 30)) {
				neighbor.goodNotificationsCount = 0;
			}
		}
		
		uploadStats.add(sentCount);
		downloadStats.add(receivedCount);
		
		this.setPrevReceivedCount(receivedCount);
		sentCount = 0;
		receivedCount = 0;
	}
	
	public void addNeighbor(int id) {
		if (!neighborQueue.contains(overlay.getHost(id)) && !outstandingNeighbors.contains(overlay.getHost(id))) {
			neighborQueue.add(overlay.getHost(id));
			neighbors.put(new Integer(id), new Neighbor(id));
		}
	}
	
	public void addNeighbor(int ring, int id) {
		if (!neighborQueue.contains(overlay.getHost(id)) && !outstandingNeighbors.contains(overlay.getHost(id))) {
			neighborQueue.add(overlay.getHost(id));
			neighbors.put(new Integer(id), new Neighbor(id));
		}
		
		// if the ring data haven't been added yet
		
		if (neighborsByRing.isEmpty()) {
			neighborsByRing.add(ring, new HashMap());
		}
		else if (neighborsByRing.size() <= ring){
			neighborsByRing.add(ring, new HashMap());
		}
		neighborsByRing.get(ring).put(new Integer(id), new Integer(id));
	}
	
	/**
	 * Remove neighbor
	 * @param ring Ring that neighbor belongs to
	 * @param id Id of the neighbor to be removed
	 */
	public void removeNeighbor(int ring, int id) {
		boolean duplicateNeighbor = false;
		
		// this loop tries to find if Host has the same neighbor in different rings. If so, some actions cannot be done 
		for (int i = 0 ; i < neighborsByRing.size(); i++){
			
			// skip the ring that contains the neighbor that will be removed
			if (i == ring){
				continue;
			}
			if (this.neighborsByRing.get(i).containsKey(id)){
				duplicateNeighbor = true;
				break;
			}
		}
		
		if (!duplicateNeighbor){
			neighbors.remove(new Integer(id));
			neighborQueue.remove(overlay.getHost(id));
			outstandingNeighbors.remove(overlay.getHost(id));
		}
		
		this.neighborsByRing.get(ring).remove(id);
	}
	
	public void addNotify(int seqNo) {
		int index = availabilityWindow.calculateOffset(seqNo);
		
		while (chunks.size() <= index) {
			AvailableChunks newChunk = new AvailableChunks();
			newChunk.setChunkStatus(ChunkStatusEnum.NOT_HEARD_OF);
			chunks.add(newChunk);
		}

		chunks.get(index).setChunkStatus(ChunkStatusEnum.NOTIFIED_ONLY);
	}
	
	public void addPacket(int seqNo) {
		int index = availabilityWindow.calculateOffset(seqNo);
		chunks.get(index).setChunkStatus(ChunkStatusEnum.RECEIVED);
	}
	
	/**
	 * Initialize interest window and availability window
	 */
	public void initializeWindows() {
		interestWindow = new Window(StreamingConfig.MCASTINTERESTWINDOWSIZE);
		availabilityWindow = new Window(StreamingConfig.MCASTWINDOWSIZE);
		this.update();
		this.notifyPackets();
		this.requestPacket();
		this.sendPacket();
	}
	
	/** 
	 * Notify all neighbors but those that already notified abt packet
	 * @param seqNo
	 */
	public void notifyNeighbors(int seqNo) {
		Host[] n = this.getNeighbors();
		
		// PS: Am notifying even if neighbor has notified me - doubles the number of notifications
		// take this into account when calculating the total upload rate used - this is just for simulation
		for (int i = 0; i < n.length; i++) {
				int[] packet = new int[1];
				packet[0] = seqNo;
				// Creates and adds message to queue
				new Message("notify", this, n[i], packet);
				Logs.printMessage("notifyNeighbors", "[MSG00] Peer " + this.getId() + " notified chunks [" + packet[0] + "] as available to peer " + n[i].getId());
		}		
	}
	
	/**
	 * Notify neighbors that has received new packets that can be shared
	 */
	public void notifyPackets() {
		periodicNotify.addEvent();
		
		// Only send notifies if there are any packets to be notified
		if (packetsToNotify.size() > 0){
			
			int[] list = new int[packetsToNotify.size()];
			for (int i = 0; i < packetsToNotify.size(); i++) {
				list[i] = ((Integer)packetsToNotify.get(i)).intValue();
			}
			packetsToNotify.clear();
			
			Host[] n = this.getNeighbors();
			for (int i = 0; i < n.length; i++) {
				
				// Only send notify to neighbors but sourcehost
				if (n[i].getId() != 0){
				
					new Message("notify", this, n[i], list);
					
					String aux = "";
					for (int j= 0 ; j < list.length ; j++)	{
						aux += list[j] + ",";
					}
					Logs.printMessage("notifyPackets", "[MSG01] Peer " + this.getId() + " notified chunks [" + aux + "] as available to peer " + n[i].getId());
				}			
			}
		}
	}
	
	/**
	 * Handle notify message received from a neighbor Host, informing that the neighbor has a new packet available to share
	 * @param seqNo Sequence number of the packet notified
	 * @param source Host that sent the notification
	 */
	public void handleNotify(int[] seqNo, Host source) {
		// Start windows as soon as first notification is received if it wasn't created yet
		if (interestWindow == null){
			this.initializeWindows();
		}

		Neighbor n = (Neighbor)neighbors.get(new Integer(source.id));
		
		// If neighbor isn't a neighbor anymore, continue
		if (n == null){
			return;
		}
		
	
		for (int i = 0; i < seqNo.length; i++) {
						
		
			// If neighbor isn't a neighbor anymore, continue
			if (!interestWindow.isWithin(seqNo[i])) {
				return;
			}
						
			
			boolean ignoreThisNotify = false;
			// If neighbor is not in the blacklist, continue
			if (Config.USE_COMPARATOR_MODULE){
				if (this.blacklistedIds!=null && this.blacklistedIds.contains(source.getId())){
					Logs.printMessage("handleNotifyCMRelated","[CMP21] [time="+ Math.floor(Simulator.getInstance().getTime() * 100)/100+"] chunk " + seqNo[i] + " Peer " + getId() + " - received notify of chunk " + seqNo[i]+ 
							          " from blacklisted peer " + source.getId() + ". Blacklist [" + (blacklistedIds!=null ? blacklistedIds : "") + "] cidBlacklistUpdate => " + cidBlacklistUpdate );
					ignoreThisNotify=true;
				}else{
					Logs.printMessage("handleNotifyCMRelated","[CMP21] [time="+ Math.floor(Simulator.getInstance().getTime() * 100)/100+"] chunk " + seqNo[i] + " Peer " + getId() + " - received notify of chunk " + seqNo[i]+ 
					                  " from peer " + source.getId() + ". Blacklist [" + (blacklistedIds!=null ? blacklistedIds : "") + "] cidBlacklistUpdate => " + cidBlacklistUpdate );

				}
			}
			
			
			if (!ignoreThisNotify) {
				if (seqNo[i] > interestWindow.getPacketStartSeqNo() + 30) {
					n.goodNotificationsCount++;
				}
				
				ChunkStatusEnum status = getPacketStatus(seqNo[i]);
				
				if (status == ChunkStatusEnum.NOT_HEARD_OF) {
					this.addNotify(seqNo[i]);
				}
				if ((status == ChunkStatusEnum.NOT_HEARD_OF) || (status == ChunkStatusEnum.NOTIFIED_ONLY)) {
					n.addNotifiedPacket(seqNo[i]);
				}
				
				Logs.printMessage("handleNotify", "[MSG02] Peer " + this.getId() + " received notification of chunk " + seqNo[i] + " as available on peer " + source.getId());
			}
			
			// Comparator stage
			// If the simulation is configured to use the comparator module to execute the diagnose
			if (Config.USE_COMPARATOR_MODULE){
				boolean b = this.comparatorModule.chunksToBeCompared.contains(seqNo[i]);
				Simulator.getInstance().chunksToBeComparedContains++;
				if (b){
					Logs.printMessage("handleNotify", "[MSG02] Peer " + this.getId() + " received notification of chunk " + seqNo[i] + " as available on peer " + source.getId());
					this.comparatorModule.requestPacket(source, seqNo[i]);
				}
			}
			
			
		}
	}
	
	/**
	 * Search a packet to request on the notified list
	 */
	public void requestPacket() {
		
		periodicRequest.addEvent();
		auxCount++;
		
		// If there are no notifications
		if (neighborQueue.isEmpty()) {
			return;
		}

		Neighbor neighbor = null;
		
		int seqNo = -1;
		Host n = null;
		
		Logs.printMessage("requestPacket", "[MSG08] Checking on peer " + this.getId() + " a chunk to request.");
		
		for (int i = 0; i < neighborQueue.size(); i++) {
			neighbor = (Neighbor)neighbors.get(new Integer(((Host)neighborQueue.get(0)).id));
			seqNo = neighbor.getRandomNotification();
			n = (Host)neighborQueue.get(0);
			
			// check if selected an seqNo and if neighbor can send more chunks
			if (seqNo != -1) {
				break;
			}
			
			neighborQueue.remove(0);
			neighborQueue.add(n);
		}
		
		if (seqNo == -1) {
			Logs.printMessage("requestPacket", "[MSG09] Search on peer " + this.getId() + " haven't found any chunks to request.");
			return;
		}
		else if (getPacketStatus(seqNo) == ChunkStatusEnum.NOTIFIED_ONLY) {
			neighbor.addRequestedPacket(seqNo, simulator.getTime());

			// Check if host's outstanding limit of requests has been reached. - n is of class Host (unlike neighbor)
			if (neighbor.outstanding >= StreamingConfig.MCASTMAXOUTSTANDING) {
				neighborQueue.remove(n);
				outstandingNeighbors.add(n);
			}
			
			new Message("request", this, n, seqNo, false);
			setPacketStatus(seqNo, ChunkStatusEnum.PENDING);
			Logs.printMessage("requestPacket", "[MSG10] Peer " + this.getId() + " sent request of chunk " + seqNo + " to peer " + n.getId());
		}
	}
	
	/**
	 * Handle request message from neighbor
	 * @param seqNo Sequence number of packet that was requested
	 * @param source Host that requested the packet
	 * @param cm Boolean, indicates if request came from Comparator Module or not
	 */
	public void handleRequest(int seqNo, Host source, boolean cm) {
		String msgCM = "";

		if (cm){
			msgCM = " [CM] ";
		}

		Logs.printMessage("handleRequest", "[MSG04a]" + msgCM + " Peer " + this.getId() + " received request of chunk " + seqNo + " from peer " + source.getId());
		
		// Check if has the request seqNo - if is in availability window
		if (!availabilityWindow.isWithin(seqNo)){
			Logs.printMessage("handleRequest", "[MSG4b]" + msgCM + " Peer " + this.getId() + " received request of chunk " + seqNo + " from peer " + source.getId() + ". OUTSIDE AVAILABILITY WINDOW.");
			return;
		}
		
		int index = availabilityWindow.calculateOffset(seqNo);

		// Add request to the queue of requests
		requestedQueue.add(new AttendRequest(this, seqNo, chunks.get(index).getData(), source, cm));
	}
	
	/**
	 * Send packet to the neighbor that requested it
	 */
	public void sendPacket() {
		periodicSend.addEvent();
		
//		Logs.printMessage("sendPacket", "[MSG5a] Peer " + this.getId() + " starting sendPacket...");
		
		extraBalance++;
		while (extraBalance > 0) {
			if (requestedQueue.size() == 0) {
				return;
			}
			
			AttendRequest r = (AttendRequest)requestedQueue.remove(0);
//			Logs.printMessage("sendPacket", "[MSG5b] Peer " + this.getId() + " analysing new request from peer " + r.getSource().getId() + " over chunk " + r.getSeqNo() + "...");

			// Check if seqNo is within availability window
			if (!availabilityWindow.isWithin(r.getSeqNo())) {
				Logs.printMessage("sendPacket", "[MSG5c] Peer " + this.getId() + " would sent chunk " + r.getSeqNo() + " to peer " + r.getSource().getId() + ". OUTSIDE AVAILABILITY WINDOW.");			
				// If there are no more request at the queue, return
				if (requestedQueue.size() == 0) {
					return;
				}
				
				// Attend the next request on queue
				r = (AttendRequest)requestedQueue.remove(0);
//				Logs.printMessage("sendPacket", "[MSG5d] Peer " + this.getId() + " defined the request of peer " + r.getSource().getId() + ". Will send chunk " + r.getSeqNo() + "...");
			}
			
			Neighbor n = (Neighbor)neighbors.get(new Integer(r.getSource().getId())); 
		
			// If neighbor isn't a neighbor anymore, continue
			if (n == null){
				continue;
			}
			new Message("packet", this, r.getSource(), r.getSeqNo(), r.getData(), r.isRequestFromCM());
			sentCount++;
			n.totalSentCount++;
			//Simulator.getInstance().addIntTotalChunksSent();
			extraBalance--;
			
			Logs.printMessage("sendPacket", "[MSG05e] Peer " + this.getId() + " sent chunk " + r.getSeqNo() + " to peer " + r.getSource().getId() + ". DATA = [" + r.getData() + "].");
		}
	}
		
	/**
	 * Handle a packet received from a neighbor
	 * @param seqNo Sequence number from the packet received
	 * @param source The host that sent the packet
	 * @param data Data inside in packet
	 */
	public void handlePacket(int seqNo, Host source, String data, boolean messageFromCM) {
		// If the simulation is configured to use the comparator module to execute the diagnose
		if (Config.USE_COMPARATOR_MODULE && messageFromCM){
			boolean b = this.comparatorModule.chunksToBeCompared.contains(seqNo);
			Simulator.getInstance().chunksToBeComparedContains++;
			if (!b){
				if (!(source.getId() == 0)) {
					Logs.printMessage("handlePacket", "[***BUG01***] Peer " + this.getId() + " received chunk " + seqNo + " from peer " + source.getId() + ". DATA = [" + data + "].");
				}
				else {
					Logs.printMessage("handlePacket", "[MSG15] [CM] Override case. DISCARDED. Peer " + this.getId() + " received chunk " + seqNo + " from peer " + source.getId() + ". DATA = [" + data + "].");
				}
			}
			else {
				// Comparator must handle packet
				this.comparatorModule.handlePacket(seqNo, source, data);
			}
			return;
		}
		

		
		
		if (!interestWindow.isWithin(seqNo)) {
			Logs.printMessage("handlePacket", "[MSG06] Peer " + this.getId() + " received chunk " + seqNo + " from peer " + source.getId() + " outside the interest window. StarSeqNo: " + interestWindow.getPacketStartSeqNo() + " DATA = [" + data + "].");
			return;
		}
		
		Neighbor neighbor = (Neighbor)neighbors.get(new Integer(source.id));
		
		// If neighbor isn't a neighbor anymore, continue
		if (neighbor == null){
			return;
		}
		
		// Try to remove the requested packet from neighbors outstanding controller. If couldn't remove, return false
		boolean removed = neighbor.removeRequestedPacket(seqNo);
		
		if (!removed){
			Logs.printMessage("handlePacket", "[MSG11] Problem with removing request packet in handlePacket. Possible timed out received packet.");
		}
		
		// Remove neighbor from outstanding list if it is in the list
		if (outstandingNeighbors.contains(source)) {
			outstandingNeighbors.remove(source);		
			neighborQueue.add(source);
		}
		
		ChunkStatusEnum status = getPacketStatus(seqNo);
		if (status != ChunkStatusEnum.RECEIVED) {
			Logs.printMessage("handlePacket", "[MSG06] Peer " + this.getId() + " received chunk " + seqNo + " from peer " + source.getId() + ". DATA = [" + data + "].");
			
			setPacketStatus(seqNo, ChunkStatusEnum.RECEIVED);

			
			
			if (!data.contains("modified")){
				Simulator.getInstance().addIntOriginalChunksCounter();

				// -----------------------------------------------------------
				// Registra # PEERS que receberam CHUNKS *originais* / segundo
				// -----------------------------------------------------------

				if (Simulator.getInstance().getTimeTrunc() < (Config.TIME + StreamingConfig.MCASTWINDOWSIZE + 1)){

					// Registra que um novo chunk *original* foi recebido.
					Simulator.getInstance().originalChunksPerSecond[ Simulator.getInstance().getTimeTrunc() ]++;
				}
}
			else {
				Simulator.getInstance().addIntModifiedChunksCounter();

				

				// -----------------------------------------------------------
				// Registra # PEERS que receberam CHUNKS modificados / segundo
				// -----------------------------------------------------------
				// Register that a modified chunk was received in that momento (second) of the simulation. -- If simulation time is within array size (to avoid "Array out of bounds").
				if (Simulator.getInstance().getTimeTrunc() < (Config.TIME + StreamingConfig.MCASTWINDOWSIZE + 1)){
					
					// Registra que um novo chunk modificado foi recebido.
					Simulator.getInstance().pollutedChunksPerSecond[ Simulator.getInstance().getTimeTrunc() ]++;

					// Registra que um novo peer recebeu chunk modificado recebido.
					if ( Simulator.getInstance().currentSimulationSecond != Simulator.getInstance().getTimeTrunc() ) {

						Simulator.getInstance().currentSimulationSecond = Simulator.getInstance().getTimeTrunc();

						Simulator.getInstance().pollutedPeersInCurrentSimulationSecond = new ArrayList<Integer>();						
						Simulator.getInstance().pollutedPeersInCurrentSimulationSecond.add( this.id );
						Simulator.getInstance().pollutedPeersPerSecond[ Simulator.getInstance().getTimeTrunc() ]++;

					}
					else {
						if ( !Simulator.getInstance().pollutedPeersInCurrentSimulationSecond.contains( this.id ) ) {
							Simulator.getInstance().pollutedPeersInCurrentSimulationSecond.add( this.id );
							Simulator.getInstance().pollutedPeersPerSecond[ Simulator.getInstance().getTimeTrunc() ]++;
						}
					}
				}
				// ---------------------------------------------


				
				// ---------------------------------------------
				// log_poluted_peers.txt
				// ---------------------------------------------
				String strConfigData = new String();
				strConfigData = "Peer " + Util.lpad(this.getId(),4) + " | at time= " +  Util.lpad( Util.to_char( Simulator.getInstance().getTime() ,2), 6) + " | received POLLUTED chunk " + Util.lpad(seqNo,4) + " | from peer " + Util.lpad( source.getId() ,4) + "\n";
				FileLog.getInstance().saveExtraLogInFile("FinalStats", "log_poluted_peers", strConfigData);
				// ---------------------------------------------
				
				
				
				// Adds polluted peers on list
				if (Simulator.getInstance().getPollutedPeers().get(seqNo) == null){
					Map<Integer, Integer> newInsertion = new HashMap<Integer, Integer>();
					newInsertion.put(this.getId(), this.getId());
					
					Simulator.getInstance().getPollutedPeers().put(seqNo, newInsertion);
				}
				else {
					Simulator.getInstance().getPollutedPeers().get(seqNo).put(this.getId(), this.getId());
				}

								//System.out.println("isPolluted: " + Simulator.getInstance().isPolluted[seqNo].size());
								// adiciona no array isPolluted, na linha seqNo, inserir o this.getId() no sub-array.     [chunk,[id_peers_poluidos1,2,3]]
								// Simulator.getInstance().getPollutedPeers().get(seqno).add-no-subarray( this.getId() );
					        	
								//System.out.println("isPolluted: "+ Simulator.getInstance().isPolluted.length);
								//System.out.println("isPolluted[]: "+ Simulator.getInstance().isPolluted[seqNo].size());

				if (!Simulator.getInstance().isPolluted[seqNo].contains((Integer)this.getId())) {
	        		Simulator.getInstance().isPolluted[seqNo].add((Integer)this.getId());
	        	}

			}
			
			
			
			
			// Put received packet on list of packets to notify
			packetsToNotify.add(new Integer(seqNo));
			
			// Increase received counter
			receivedCount++;
			Simulator.getInstance().addIntTotalChunksReceived();
			
			int index = availabilityWindow.calculateOffset(seqNo);
			
			// Store data from the chunk
			this.chunks.get(index).setData(data);
		}
		else if ((status != ChunkStatusEnum.RECEIVED) && !(this instanceof Attacker2)) {
			Logs.printMessage("handlePacket", "Problems in status when receiving packet " + status +  " received from " + source.id);
		}
		
		// Get neighbors
		Neighbor[] ns = (Neighbor[])neighbors.values().toArray(new Neighbor[neighbors.size()]);
		
		// Remove sequence number from list of notified packets of other neighbors
		for (int i = 0; i < ns.length; i++) {
			ns[i].removeNotifiedPacket(seqNo);
		}
	}

	/**
	 * Handle override caused by source
	 * @param oldSeqNo Sequence number of packet originally requested
	 * @param newSeqNo Sequence number of packet that overrode the original request
	 * @param source Host that overrode the request and sent the packet - usually the source
	 * @param data Data inside in packet
	 */
	public void handleOverride(int oldSeqNo, int newSeqNo, Host source, String data) {
		// Check if host is interest in new packet
		if (!interestWindow.isWithin(newSeqNo)){
			return;
		}
		
		// Get neighbor
		Neighbor neighbor = (Neighbor)neighbors.get(new Integer(source.id));
		
		// If neighbor isn't a neighbor anymore, continue
		if (neighbor == null){
			return;
		}
		
		// Old packet could have gotten recycled before arrival of override
		if (neighbor.removeRequestedPacket(oldSeqNo)) {
			if (outstandingNeighbors.contains(source)) {
				outstandingNeighbors.remove(source);		
				neighborQueue.add(source);
			}
		}
		
		ChunkStatusEnum status = getPacketStatus(newSeqNo);
		
		if (status != ChunkStatusEnum.RECEIVED) {
			if (status == ChunkStatusEnum.NOT_HEARD_OF) {
				this.addNotify(newSeqNo);	
			}
			setPacketStatus(newSeqNo, ChunkStatusEnum.RECEIVED);

			// Put received packet on list of packets to notify
			packetsToNotify.add(new Integer(newSeqNo));
			
			// Increase received counter
			receivedCount++;
			Simulator.getInstance().addIntTotalChunksReceived();
			
			int index = availabilityWindow.calculateOffset(newSeqNo);
			
			// Store data from the chunk
			this.chunks.get(index).setData(data);
			
			Logs.printMessage("handleOverride", "[MSG06] Peer " + this.getId() + " received chunk " + newSeqNo + " from peer " + source.getId() + ". DATA = [" + data + "].");
		}
		Neighbor n = (Neighbor)neighbors.get(new Integer(source.id));
		n.addNotifiedPacket(oldSeqNo);
		
		// Set packet status as notified only
		this.setPacketStatus(oldSeqNo, ChunkStatusEnum.NOTIFIED_ONLY);
	}
	
	/**
	 * Print stats of simulation
	 */
	public void printStats() {
		
		String Aux;
		Aux = "Node: " + id + "**" + (int) downloadStats.sum() + "** " + " || Down: " + Math.round(downloadStats.avg()*StreamingConfig.PACKETSIZE /1000)+ 
									"  ||  Up: " + Math.round(uploadStats.avg()*StreamingConfig.PACKETSIZE/1000) + " || Neighbors: ";
				
		int[] ids = this.getNeighborIds();
		for (int i = 0; i < ids.length; i++) {
			Aux += ids[i] + "(" + ((Neighbor)neighbors.get(new Integer(ids[i]))).totalSentCount  + ") - ";
		}
		
		Logs.printMessage("printStats", Aux);
	}
	
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}
	
	public int[] getNeighborIds(){
		Object[] keys = (neighbors.keySet().toArray());
		int[] n = new int[keys.length];
		for (int i = 0; i < keys.length; i++)
			n[i] = ((Integer)keys[i]).intValue();
		return n;
	}
	
	public Host[] getNeighbors() {
		ArrayList temp = new ArrayList();
		temp.addAll(neighborQueue);
		temp.addAll(outstandingNeighbors);
		Host[] array = new Host[temp.size()];
		return (Host[]) temp.toArray(array);
	}

	public HashMap getNeighborsMap(){
		return this.neighbors;
	}
	
	public double getNeighborLatency(int id) {
		return ((Neighbor)neighbors.get(new Integer(id))).latency;
	}
	
	public void setNeighborLatency(int id, double latency) {
		if (neighbors.containsKey(id)){
			((Neighbor)(neighbors.get(new Integer(id)))).latency = latency;
		}
		else {
			Logs.printMessage("setNeighborLatency", "Peer " + id + " could't be found by peer" + this.getId() + ".");
		}
		
	}
	
	public ChunkStatusEnum getPacketStatus(int seqNo) {
		int index = availabilityWindow.calculateOffset(seqNo);
		if (index < 0)
			return ChunkStatusEnum.NOT_INTERESTED;
		if ((index >= 0) && (index < chunks.size()))
			return chunks.get(index).getChunkStatus();
		return ChunkStatusEnum.NOT_HEARD_OF;
	}
	
	public void setPacketStatus(int seqNo, ChunkStatusEnum status) {
		int index = availabilityWindow.calculateOffset(seqNo);
		if (index < 0)
			return;
        chunks.get(index).setChunkStatus(status);
	}

	public int getPrevReceivedCount() {
		return prevReceivedCount;
	}

	public void setPrevReceivedCount(int prevReceivedCount) {
		this.prevReceivedCount = prevReceivedCount;
	}
	
	public int getNumberNeighbors() {
		return neighbors.size();
	}

	public ComparatorModule getComparatorModule() {
		return comparatorModule;
	}

	public void setComparatorModule(ComparatorModule comparatorModule) {
		this.comparatorModule = comparatorModule;
	}
}
